// This interface declares the required functionality for a hypercert token
// notice This interface does not specify the underlying token type (e.g. 721 or 1155)
interface IAllowlist {
function isAllowedToClaim(
bytes32[] calldata proof,
uint256 tokenID,
bytes32 leaf
) external view returns (bool isAllowed);
}
藉由merkle proof的方式與allowlist 互動+實作
包含兩個Event
引入 oz-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol 需再往上查verifyCalldata如何實作
event AllowlistCreated(uint256 tokenID, bytes32 root);
event LeafClaimed(uint256 tokenID, bytes32 leaf);
Merkle tree相關變數
mapping(uint256 => bytes32) internal merkleRoots;
查詢+proof => isAllowed = MerkleProofUpgradeable.verifyCalldata(proof, merkleRoots[claimID], leaf);
function _createAllowlist(uint256 claimID, bytes32 merkleRoot, uint256 units) internal {
if (merkleRoot == "" || units == 0) revert Errors.Invalid();
if (merkleRoots[claimID] != "") revert Errors.DuplicateEntry();
merkleRoots[claimID] = merkleRoot;
maxUnits[claimID] = units;
emit AllowlistCreated(claimID, merkleRoot);
}
function _processClaim(bytes32[] calldata proof, uint256 claimID, uint256 amount) internal {
if (merkleRoots[claimID].length == 0) revert Errors.DoesNotExist();
bytes32 leaf = _calculateLeaf(msg.sender, amount);
if (hasBeenClaimed[claimID][leaf]) revert Errors.AlreadyClaimed();
if (
!MerkleProofUpgradeable.verifyCalldata(proof, merkleRoots[claimID], leaf) ||
(minted[claimID] + amount) > maxUnits[claimID]
) revert Errors.Invalid();
hasBeenClaimed[claimID][leaf] = true;
emit LeafClaimed(claimID, leaf);
}
function _calculateLeaf(address account, uint256 amount) internal pure returns (bytes32 leaf) {
leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));
}
Murky contains contracts that can generate merkle roots and proofs. Murky also performs inclusion verification. Both XOR-based and a concatenation-based hashing are currently supported.
The root generation, proof generation, and verification functions are all fuzz tested (configured 5,000 runs by default) using arbitrary bytes32 arrays and uint leaves. There is also standardized testing and differential testing.
用共同的MerkleBase合約後,使用不同的HASHING FUNCTION(但都以Assembly書寫有點超出難度了)
/**********************
* PROOF VERIFICATION *
**********************/
function verifyProof(bytes32 root, bytes32[] memory proof, bytes32 valueToProve) external pure returns (bool) {
// proof length must be less than max array size
bytes32 rollingHash = valueToProve;
uint256 length = proof.length;
unchecked {
for(uint i = 0; i < length; ++i){
rollingHash = hashLeafPairs(rollingHash, proof[i]);
}
}
return root == rollingHash;
}
op寫法 單一x2(verify + process)x2 (X + calldata)-> Batch x4
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*/
*/
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
Merkle proof僅再與allowlist有關之mint的驗證實作,藉由呼叫_processClaim function
function mintClaimFromAllowlist(
address account,
bytes32[] calldata proof,
uint256 claimID,
uint256 units
) external whenNotPaused {
_processClaim(proof, claimID, units);
_mintToken(account, claimID, units);
}
function batchMintClaimsFromAllowlists(
address account,
bytes32[][] calldata proofs,
uint256[] calldata claimIDs,
uint256[] calldata units
) external whenNotPaused {
uint256 len = claimIDs.length;
for (uint256 i; i < len; ) {
_processClaim(proofs[i], claimIDs[i], units[i]);
unchecked {
++i;
}
}
_batchMintTokens(account, claimIDs, units);
}
solidity by example之介紹及影片介紹